/*
 * Copyright 2009 - sanatkumar.padhy
 * 
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *  
 *  	http://www.apache.org/licenses/LICENSE-2.0
 *  
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS, 
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and 
 *  limitations under the License. 
 */
package com.googlecode.simplecrud.generator;

import static org.apache.velocity.runtime.RuntimeConstants.RUNTIME_LOG_LOGSYSTEM_CLASS;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;

import org.apache.velocity.Template;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.Velocity;

import com.googlecode.simplecrud.generator.db.ColumnMetadata;
import com.googlecode.simplecrud.generator.db.DbIntrospector;
import com.googlecode.simplecrud.generator.model.ColumnOverride;
import com.googlecode.simplecrud.generator.model.DataSource;
import com.googlecode.simplecrud.generator.model.IgnoreColumn;
import com.googlecode.simplecrud.generator.model.SimpleCrudConfig;
import com.googlecode.simplecrud.generator.model.Table;

/**
 * Generator
 */
public class Generator
{
	// initialize velocity engine
	static {
		Properties props = new Properties();
		props.put(RUNTIME_LOG_LOGSYSTEM_CLASS, "org.apache.velocity.runtime.log.NullLogSystem");
		props.put("resource.loader", "class");
		props.put("class.resource.loader.class", "org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader");
		try {
			Velocity.init(props);
		}
		catch (Exception e) {
			throw new GeneratorException("Error:: Velocity initialization - ", e);
		}
	}

	/**
	 * @param args
	 */
	public static void main(String[] args)
	{
		if ((args.length < 1) || (args.length > 1)) { throw new GeneratorException("usage:: Generator <configurationfile>"); }

		VelocityContext vContext = null;
		BufferedWriter objectWriter, keyWriter = null;
		Set<String> objectImports, keyImports = null;

		SimpleCrudConfig config = loadConfiguration(args[0]);
		String packageName = config.getTargetPackage().trim();
		String targetProject = config.getTargetProject();
		Connection connection = createConnection(config.getDataSource());

		DbIntrospector introspector = new DbIntrospector();
		introspector.setConnection(connection);

		// Obtain the velocity templates
		Template objectTemplate, keyTemplate = null;
		try {
			objectTemplate = Velocity.getTemplate("persistenceObject.vm");
			keyTemplate = Velocity.getTemplate("persistenceKey.vm");
		}
		catch (Exception e) {
			throw new GeneratorException("Error obtaining velocity templates ", e);
		}

		for (Table table : config.getTable()) {
			objectImports = new HashSet<String>();
			keyImports = new HashSet<String>();
			vContext = new VelocityContext();

			Map<String, ColumnMetadata> columnMap = introspector.getIntrospectedTable(table.getName());

			List<IgnoreColumn> ignoredColumns = table.getIgnoreColumn();

			for (IgnoreColumn ignoreColumn : ignoredColumns) {
				columnMap.get(ignoreColumn.getName()).setIgnore(true);
			}

			List<ColumnOverride> columnOverrides = table.getColumnOverride();
			for (ColumnOverride columnOverride : columnOverrides) {
				ColumnMetadata column = columnMap.get(columnOverride.getName().toUpperCase());
				if (columnOverride.getJavaType() != null) {
					column.setJavaType(columnOverride.getJavaType());
				}
				if (columnOverride.getTypeConverter() != null) {
					column.setTypeConverter(columnOverride.getTypeConverter());
				}
				column.setProperty(columnOverride.getProperty());
				column.setPrimaryKey(columnOverride.isPrimaryKey());
			}

			for (ColumnMetadata column : columnMap.values()) {
				if (!"byte[]".equals(column.getJavaType())) {
					objectImports.add(column.getJavaType());
					if (column.isPrimaryKey()) {
						keyImports.add(column.getJavaType());
					}
				}
			}

			// Copy column name as property name
			for (ColumnMetadata metadata : columnMap.values()) {
				if (metadata.getProperty() == null) {
					metadata.setProperty(metadata.getColumnName().toLowerCase());
				}
			}

			vContext.put("imports", objectImports);
			vContext.put("keyImports", keyImports);
			vContext.put("class", table);
			vContext.put("columnMap", columnMap.values());
			vContext.put("utility", new GeneratorUtility());
			vContext.put("package", packageName);

			try {
				String persistenceObject = table.getValueObject() + "Object.java";
				String persistenceKey = table.getValueObject() + "Key.java";

				if (targetProject != null) {
					String targetDirectory = getDirectory(targetProject, packageName).getAbsolutePath().trim();
					File objectFile = new File(targetDirectory, persistenceObject);
					objectWriter = new BufferedWriter(new FileWriter(objectFile, false));
					File keyFile = new File(targetDirectory, persistenceKey);
					keyWriter = new BufferedWriter(new FileWriter(keyFile, false));
				}
				else {
					objectWriter = new BufferedWriter(new FileWriter(persistenceObject, false));
					keyWriter = new BufferedWriter(new FileWriter(persistenceKey, false));
				}

				objectTemplate.merge(vContext, objectWriter);
				objectWriter.flush();
				objectWriter.close();
				keyTemplate.merge(vContext, keyWriter);
				keyWriter.flush();
				keyWriter.close();
			}
			catch (IOException e) {
				throw new GeneratorException("IO Error writing " + table.getValueObject(), e);
			}
		}
	}

	private static Connection createConnection(DataSource dataSource)
	{
		try {
			Class.forName(dataSource.getDriver());
			return DriverManager.getConnection(dataSource.getUrl(), dataSource.getUser(), dataSource.getPassword());
		}
		catch (ClassNotFoundException e) {
			throw new GeneratorException("Driver not loaded :: ", e);
		}
		catch (SQLException e) {
			throw new GeneratorException("Error getting a connection :: ", e);
		}
	}

	/**
	 * @return SimpleCrudConfig
	 */
	private static SimpleCrudConfig loadConfiguration(String configFile)
	{
		SimpleCrudConfig config = null;
		JAXBContext context;
		FileReader reader;
		try {
			File file = new File(configFile);
			reader = new FileReader(file);
			context = JAXBContext.newInstance("com.googlecode.simplecrud.generator.model");
			Unmarshaller m = context.createUnmarshaller();
			config = (SimpleCrudConfig) m.unmarshal(reader);
			reader.close();
		}
		catch (IOException e) {
			throw new GeneratorException("Error reading file: " + configFile, e);
		}
		catch (JAXBException e) {
			throw new GeneratorException("Error during unmarshalling", e);
		}
		return config;
	}

	/**
	 * @param targetProject
	 * @param targetPackage
	 * @return
	 */
	private static File getDirectory(String targetProject, String targetPackage)
	{
		File project = new File(targetProject);
		if (!project.isDirectory()) { throw new GeneratorException("targetProject is not a directory"); }

		File directory = new File(project, targetPackage.replace('.', '\\'));
		if (directory.isDirectory()) {
			boolean created = directory.mkdir();
			System.out.println("Directory Created :: " + created);
		}
		return directory;
	}
}
